package com.googlecode.quillen.cli;

import org.apache.commons.cli.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jets3t.service.security.AWSCredentials;
import org.jets3t.service.impl.rest.httpclient.RestS3Service;
import org.jets3t.service.S3ServiceException;
import com.googlecode.quillen.service.*;
import com.googlecode.quillen.service.ChunkService;
import com.googlecode.quillen.service.ChunkServiceImpl;
import com.googlecode.quillen.service.CompressionService;
import com.googlecode.quillen.service.CompressionServiceImpl;
import com.googlecode.quillen.application.Backup;
import com.googlecode.quillen.application.BackupImpl;
import com.googlecode.quillen.application.*;
import com.googlecode.quillen.util.WorkQueueAbortedException;
import com.googlecode.quillen.util.ResultConsumer;
import static com.googlecode.quillen.util.Utils.logInfo;
import static com.googlecode.quillen.util.Utils.logError;
import com.googlecode.quillen.domain.ObjectStorageException;
import com.googlecode.quillen.domain.AttributeStorageException;
import com.googlecode.quillen.domain.Snapshot;
import com.googlecode.quillen.domain.FileInfo;
import com.googlecode.quillen.repository.AttributeStorage;
import com.googlecode.quillen.repository.ObjectStorage;
import com.googlecode.quillen.repository.aws.S3ObjectStorageImpl;
import com.googlecode.quillen.repository.aws.SDBAttributeStorageImpl;
import com.xerox.amazonws.sdb.SimpleDB;

import java.io.*;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.text.*;
import java.text.ParseException;

public class Console
{
    private static final Log LOG = LogFactory.getLog(Console.class);

    public static void main(String[] args)
    {
        try
        {
            Options options = getOptions();

            CommandLine cmdline = new GnuParser().parse(options, args);

            validateCommandLine(cmdline);

            if (cmdline.hasOption("version"))
            {
                printVersion();
            }
            else if (cmdline.hasOption("command"))
            {
                executeCommand(cmdline.getOptionValue("command"), cmdline);
            }
            else
            {
                printHelp(options);
            }
        }
        catch (Exception ex)
        {
            logError(LOG, ex, null, "error");
        }
    }

    @SuppressWarnings("static-access")
    private static Options getOptions()
    {
        Options options = new Options();

        options.addOption("help", false, "print this message and exit");

        options.addOption("version", false, "print the version and exit");

        options.addOption(OptionBuilder.withArgName("command")
                .hasArg()
                .withDescription("the command to execute (backup, restore, list, delete, install, uninstall)")
                .create("command"));

        options.addOption(OptionBuilder.withArgName("base path")
                .hasArg()
                .withDescription("the base path to backup files from (defaults to the current directory)")
                .create("base"));

        options.addOption(OptionBuilder.withArgName("path name")
                .hasArg()
                .withDescription("the local path name relative to the base path")
                .create("path"));

        options.addOption(OptionBuilder.withArgName("path list")
                .hasArg()
                .withDescription("filename containing a list of paths (- for stdin)")
                .create("pathlist"));

        options.addOption(OptionBuilder.withArgName("snapshot name")
                .hasArgs()
                .withDescription("space separated list of snapshots for this backup (defaults to the current time)")
                .create("snapshot"));

        options.addOption(OptionBuilder.withArgName("prefix")
                .hasArg()
                .withDescription("constrain to values with this prefix")
                .create("prefix"));

        options.addOption(OptionBuilder.withArgName("maximum date")
                .hasArg()
                .withDescription("constrain to snapshots less than or equal to this date and time (in format YYYYMMddHHmmss)")
                .create("date"));

        return options;
    }

    private static void validateCommandLine(CommandLine cmdline)
    {
        // TODO: ensure it's a valid command
        // TODO: ensure required options are present depending on the command
    }

    private static void printHelp(Options options)
    {
        new HelpFormatter().printHelp("quillen", options);
    }

    private static void printVersion()
    {
        // TODO: use maven-antrun-plugin preprocessing to substitute the version from the pom file?

        logInfo(LOG, "0.4-SNAPSHOT");
    }

    private static void executeCommand(String command, CommandLine cmdline)
            throws ObjectStorageException, IOException, WorkQueueAbortedException, NoSuchAlgorithmException, AttributeStorageException, S3ServiceException, InterruptedException, ParseException
    {
        Properties config = new Properties();
        config.load(new FileInputStream(StringUtils.defaultIfEmpty(cmdline.getOptionValue("config"), "quillen.properties")));

        AWSCredentials credentials = new AWSCredentials(config.getProperty("quillen.aws.key"), config.getProperty("quillen.aws.secret"));
        CompressionService compressionService = new CompressionServiceImpl();
        AttributeStorage attributeStorage = new SDBAttributeStorageImpl(new SimpleDB(credentials.getAccessKey(), credentials.getSecretKey()));
        ObjectStorage objectStorage = new S3ObjectStorageImpl(new RestS3Service(credentials));
        ChunkService chunkService = new ChunkServiceImpl(compressionService, attributeStorage, objectStorage);
        ShadowFileService shadowFileService = new ShadowFileServiceImpl(compressionService, attributeStorage, objectStorage);
        SnapshotService snapshotService = new SnapshotServiceImpl(attributeStorage);
        Admin admin = new AdminImpl(chunkService, shadowFileService, snapshotService);
        Backup backup = new BackupImpl(chunkService, shadowFileService, snapshotService);
        Restore restore = new RestoreImpl(chunkService, shadowFileService);

        if ("backup".equalsIgnoreCase(command))
        {
            executeBackup(backup, cmdline);
        }
        else if ("restore".equalsIgnoreCase(command))
        {
            executeRestore(restore, cmdline);
        }
        else if ("install".equalsIgnoreCase(command))
        {
            executeInstall(admin);
        }
        else if ("uninstall".equalsIgnoreCase(command))
        {
            executeUninstall(admin);
        }
        else if ("list".equalsIgnoreCase(command))
        {
            executeList(admin, cmdline);
        }
        else if ("delete".equalsIgnoreCase(command))
        {
            executeDelete(admin, cmdline);
        }
    }

    private static void executeBackup(Backup backup, CommandLine cmdline)
            throws NoSuchAlgorithmException, IOException, AttributeStorageException, ObjectStorageException, WorkQueueAbortedException, InterruptedException, ParseException
    {
        String base = StringUtils.defaultIfEmpty(cmdline.getOptionValue("base"), SystemUtils.USER_DIR);
        String path = cmdline.getOptionValue("path");   // Can be empty
        String pathList = cmdline.getOptionValue("pathlist");   // Can be empty
        List<String> snapshots = getOptionValueList(cmdline, "snapshot");

        if (snapshots.isEmpty())
        {
            snapshots = Arrays.asList(DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"));
        }

        ResultConsumer<FileInfo> consumer = new ResultConsumer<FileInfo>()
        {
            public void newResult(FileInfo result)
            {
                System.out.printf("[%s] %d %s %s\n", result.getSnapshot(), result.getSize(), result.getDate(), result.getFilename());
            }

            public void newError(String message, Throwable t)
            {
                System.out.println(message + ": " + t.getMessage());
            }
        };

        if (StringUtils.isEmpty(pathList))
        {
            backup.backup(base, path, snapshots, consumer);
        }
        else
        {
            List<String> paths = new ArrayList<String>();

            BufferedReader in;
            if ("-".equals(pathList))
            {
                in = new BufferedReader(new InputStreamReader(System.in));
            }
            else
            {
                in = new BufferedReader(new FileReader(pathList));
            }
            try
            {
                String line;
                while ((line = in.readLine()) != null)
                {
                    paths.add(line);
                }
            }
            finally
            {
                in.close();
            }

            backup.backup(base, paths, snapshots, consumer);
        }
    }

    private static void executeRestore(Restore restore, CommandLine cmdline)
            throws NoSuchAlgorithmException, IOException, AttributeStorageException, ObjectStorageException, WorkQueueAbortedException, ParseException
    {
        String snapshot = cmdline.getOptionValue("snapshot");     // Required
        String base = StringUtils.defaultIfEmpty(cmdline.getOptionValue("base"), SystemUtils.USER_DIR);
        String prefix = cmdline.getOptionValue("prefix");   // Can be empty

        restore.restore(snapshot, base, prefix, new ResultConsumer<String>()
        {
            public void newResult(String result)
            {
                System.out.println(result);
            }

            public void newError(String message, Throwable t)
            {
                System.out.println(message + ": " + t.getMessage());
            }
        });
    }

    private static void executeInstall(Admin admin)
            throws AttributeStorageException, ObjectStorageException, WorkQueueAbortedException
    {
        admin.install();

        System.out.println("installed object buckets and attribute databases");
    }

    private static void executeUninstall(Admin admin)
            throws AttributeStorageException, ObjectStorageException, WorkQueueAbortedException
    {
        admin.uninstall();

        System.out.println("uninstalled object buckets and attribute databases");
    }

    private static void executeList(Admin admin, CommandLine cmdline)
            throws AttributeStorageException, ParseException
    {
        List<String> snapshots = getOptionValueList(cmdline, "snapshot"); // Can be empty
        String prefix = cmdline.getOptionValue("prefix");   // Can be empty
        String date = cmdline.getOptionValue("date");       // Can be empty

        Date maxDate = null;
        if (StringUtils.isNotEmpty(date))
        {
            maxDate = new SimpleDateFormat("yyyyMMddHHmmss").parse(date);
        }

        if (!snapshots.isEmpty())
        {
            admin.listSnapshotContents(snapshots, prefix, new ResultConsumer<FileInfo>()
            {
                public void newResult(FileInfo result)
                {
                    System.out.printf("[%s] %d %s %s\n", result.getSnapshot(), result.getSize(), result.getDate(), result.getFilename());
                }

                public void newError(String message, Throwable t)
                {
                    System.out.println(message + ": " + t.getMessage());
                }
            });
        }
        else
        {
            admin.listSnapshots(prefix, maxDate, new ResultConsumer<Snapshot>()
            {
                public void newResult(Snapshot result)
                {
                    System.out.printf("[%s] %d %d %s\n", result.getName(), result.getFiles(), result.getSize(), result.getDate());
                }

                public void newError(String message, Throwable t)
                {
                    System.out.println(message + ": " + t.getMessage());
                }
            });
        }
    }

    private static void executeDelete(Admin admin, CommandLine cmdline)
            throws IOException, AttributeStorageException, ObjectStorageException, ParseException, WorkQueueAbortedException
    {
        List<String> snapshots = getOptionValueList(cmdline, "snapshot"); // Can be empty
        String prefix = cmdline.getOptionValue("prefix");   // Can be empty
        String date = cmdline.getOptionValue("date");       // Can be empty

        Date maxDate = null;
        if (StringUtils.isNotEmpty(date))
        {
            maxDate = new SimpleDateFormat("yyyyMMddHHmmss").parse(date);
        }

        ResultConsumer<FileInfo> consumer = new ResultConsumer<FileInfo>()
        {
            public void newResult(FileInfo result)
            {
                System.out.printf("[%s] %d %s %s\n", result.getSnapshot(), result.getSize(), result.getDate(), result.getFilename());
            }

            public void newError(String message, Throwable t)
            {
                System.out.println(message + ": " + t.getMessage());
            }
        };

        if (!snapshots.isEmpty())
        {
            admin.deleteSnapshots(snapshots, consumer);
        }
        else if (StringUtils.isNotEmpty(prefix) || maxDate != null)
        {
            admin.deleteSnapshots(prefix, maxDate, consumer);
        }
    }

    private static List<String> getOptionValueList(CommandLine cmdline, String option)
    {
        String[] values = cmdline.getOptionValues(option);

        if (values != null)
        {
            return Arrays.asList(values);
        }
        else
        {
            return Collections.emptyList();
        }
    }
}
